#include "codegen.h"
#include "temp.h"
#include <string.h>
#include <stdlib.h>

#if   defined(_MSC_VER) && (_MSC_VER>1000)
#pragma warning(disable:4267)
#endif

// I use nasm as the assembly compiler, so that everything here
// uses nasm syntax

static AS_instrList instrFirst = NULL;
static void appendInstr(AS_instr inst)
{
    char *newchar, *oldchar, **ptr;
    static AS_instrList instrLast = NULL;

    switch(inst->kind)
    {
    case I_OPER:
        ptr = &inst->u.OPER.assem;
        break;
    case I_LABEL:
        ptr = &inst->u.LABEL.assem;
        break;
    case I_MOVE:
        ptr = &inst->u.MOVE.assem;
        break;
    default:
        assert(0);
    }
    // copy the string
    oldchar = *ptr;
    newchar = checked_malloc(strlen(oldchar)+1);
    strcpy(newchar, oldchar);
    *ptr = newchar;

    if (instrLast)
    {
        instrLast->tail = AS_InstrList(inst, NULL);
        instrLast = instrLast->tail;
    }
    else
        instrLast = instrFirst = AS_InstrList(inst, NULL);
};

Temp_tempList L(Temp_temp h, Temp_tempList t){ return Temp_TempList(h,t);};
Temp_tempList SL(Temp_temp h) { return Temp_TempList(h, NULL);};
Temp_tempList DL(Temp_temp a, Temp_temp b) { return Temp_TempList(a, \
                                                    Temp_TempList(b, NULL));}

static Temp_temp munchExp(T_exp exp);

static Temp_temp munchExp(T_exp exp)
{
    char buf[256];
    Temp_temp result = Temp_newtemp();

    switch(exp->kind)
    {
        case T_BINOP:
        {
            Temp_tempList src = NULL;
            int con = 0;

            if (exp->u.BINOP.left->kind == T_CONST
            || exp->u.BINOP.right->kind == T_CONST)
            {
                if (exp->u.BINOP.left->kind == T_CONST)
                {
                    con = exp->u.BINOP.left->u.CONST;
                    src = SL(munchExp(exp->u.BINOP.right));
                }
                else
                {
                    con = exp->u.BINOP.right->u.CONST;
                    src = SL(munchExp(exp->u.BINOP.left));
                }


                switch(exp->u.BINOP.op)
                {
                case T_plus:
                    sprintf(buf,"ADD `d0, %d", con);
                    break;
                case T_minus:
                    sprintf(buf,"SUB `d0, %d", con);
                    break;
                case T_mul:
                    sprintf(buf,"IMUL `d0, %d", con);
                    break;
                case T_div:
                    sprintf(buf,"IDIV `d0, %d", con);
                    break;
                default:
                    assert(0);
                    return NULL;
                };

                appendInstr(AS_Oper(buf, src, NULL, NULL));
                return src->head;
            }

            switch(exp->u.BINOP.op)
            {
            case T_plus:
                sprintf(buf,"ADD `d0, `s0");
                break;
            case T_minus:
                sprintf(buf,"SUB `d0, `s0");
                break;
            case T_mul:
                sprintf(buf,"IMUL `d0, `s0");
                break;
            case T_div:
                sprintf(buf,"IDIV `d0, `s0");
                break;
            default:
                assert(0);
                return NULL;
            }
            appendInstr(AS_Oper(buf, SL(result), 
                    SL(munchExp(exp->u.BINOP.right)),
                    NULL));
            return result;
        }
        case T_MEM:
        {
            appendInstr(AS_Move("MOV `d0, [`s0]", SL(result), 
                    SL(munchExp(exp->u.MEM))));
            return result;
        }
        case T_TEMP:
        {
            // everytime we meet a temp var, create a new temp var
            // and assign value to the new one. dumplicated uses of
            // temp var will be removed in register allocation routine,
            // so we don't need to worry about the overhead
            appendInstr(AS_Move("MOV `d0, `s0", SL(result),
                    SL(exp->u.TEMP)));
            return result;
        }
        case T_ESEQ:
        case T_NAME:
            assert(0);
        case T_CONST:
        {
            Temp_temp r = Temp_newtemp();
            sprintf(buf, "MOV `d0, %d", exp->u.CONST);
            appendInstr(AS_Move(buf, SL(r), NULL));
            return r;
        }
        case T_CALL:
        {
            assert(0);
            break;
        }
    }
    assert(0);
    return NULL;
}

static void munchStm(T_stm stm)
{
    //char* buf = checked_malloc(sizeof(char)*32);
    char buf[256];

    switch(stm->kind)
    {
        case T_SEQ:
            assert(0);
            break;
        case T_LABEL:
            sprintf(buf,"%s", S_name(stm->u.LABEL));
            appendInstr(AS_Label(buf, stm->u.LABEL));
            return;
        case T_CJUMP:
        {
            Temp_label trues = stm->u.CJUMP.true;
            Temp_label falses = stm->u.CJUMP.false;
            sprintf(buf, "CMP `d0, `s0");
            appendInstr(AS_Oper(buf, 
                    SL(munchExp(stm->u.CJUMP.left)),
                    SL(munchExp(stm->u.CJUMP.right)),
                    NULL));
            switch(stm->u.CJUMP.op)
            {
                case T_eq:
                    sprintf(buf, "JE %s", S_name(trues));
                    break;
                case T_ne:
                    sprintf(buf, "JNE %s", S_name(trues));
                    break;
                case T_lt:
                    sprintf(buf, "JL %s", S_name(trues));
                    break;
                case T_le:
                    sprintf(buf, "JLE %s", S_name(trues));
                    break;
                case T_gt:
                    sprintf(buf, "JG %s", S_name(trues));
                    break;
                case T_ge:
                    sprintf(buf, "JGE %s", S_name(trues));
                    break;
                case T_ult:
                case T_ule:
                case T_ugt:
                case T_uge:
                    assert(0);
                    break;
            }
            appendInstr(AS_Oper(buf, NULL, NULL, 
                    AS_Targets(Temp_LabelList(trues, NULL)) ));

            sprintf(buf, "JMP %s", S_name(falses));
            appendInstr(AS_Oper(buf, NULL, NULL,
                    AS_Targets(Temp_LabelList(falses, NULL)) ));
            return;
        }
        case T_JUMP:
        {
            sprintf(buf, "JMP %s", S_name(stm->u.JUMP.jumps->head));
            appendInstr(AS_Oper(buf, NULL, NULL, 
                AS_Targets(stm->u.JUMP.jumps)));
            return;
        }
        case T_MOVE:
        {
            T_exp des = stm->u.MOVE.dst;
            T_exp src = stm->u.MOVE.src;

            if (des->kind == T_MEM)     //MOV [ptr], data
            {
                // MOV [ptr1], [ptr2]
                if (src->kind == T_MEM)
                {
                    // MOV temp2, [src]
                    // MOV [temp1+const], temp2
                    Temp_temp temp = munchExp(src);
                    if (des->u.MEM->kind == T_BINOP
                        && des->u.MEM->u.BINOP.left->kind == T_TEMP
                        && des->u.MEM->u.BINOP.right->kind == T_CONST)
                    {
                        assert(des->u.MEM->u.BINOP.op == T_plus);
                        sprintf(buf, "MOV [%s + %d], `s0",
                            Temp_look(Temp_name(), des->u.MEM->u.BINOP.left->u.TEMP),
                            des->u.MEM->u.BINOP.right->u.CONST);
                        appendInstr(AS_Move(buf, 
                            NULL,
                            DL(temp, des->u.MEM->u.BINOP.left->u.TEMP)));
                        return;
                    }

                    // MOV [const], temp2
                    if (des->u.MEM->kind == T_CONST)
                    {
                        sprintf(buf, "MOV [%d], `s0",
                            des->u.MEM->u.CONST);
                        appendInstr(AS_Move(buf, NULL, SL(temp)));
                        return;
                    }
                }

                // MOV [temp+const], data
                if (des->u.MEM->kind == T_BINOP
                    && des->u.MEM->u.BINOP.left->kind == T_TEMP
                    && des->u.MEM->u.BINOP.right->kind == T_CONST)
                {
                    assert(des->u.MEM->u.BINOP.op == T_plus);
                    // MOV [temp+const1], const2
                    if (src->kind == T_CONST)
                    {
                        sprintf(buf, "MOV [%s + %d], %d",
                            Temp_look(Temp_name(),des->u.MEM->u.BINOP.left->u.TEMP),
                            des->u.MEM->u.BINOP.right->u.CONST,
                            src->u.CONST);
                        appendInstr(AS_Move(buf, 
                            NULL,
                            SL(des->u.MEM->u.BINOP.left->u.TEMP)));
                        return;
                    }

                    // MOV [temp+const1], data
                    sprintf(buf, "MOV [%s + %d], `s0",
                        Temp_look(Temp_name(),des->u.MEM->u.BINOP.left->u.TEMP),
                        des->u.MEM->u.BINOP.right->u.CONST);

                    appendInstr(AS_Move(buf,
                        NULL,
                        SL(munchExp(src))));
                    return;
                }
                if (src->kind == T_CONST)
                {
                    // MOV [des], const
                    sprintf(buf, "MOV [`d0], %d",
                        src->u.CONST);
                    appendInstr(AS_Move(buf,
                        SL(munchExp(des->u.MEM)),
                        NULL));
                    return;
                }

                // other conditions
                appendInstr(AS_Move("MOV [`d0], `s0",
                    SL(munchExp(des->u.MEM)),
                    SL(munchExp(src))));
                return;
            }
            else         
            {

                appendInstr(AS_Move("MOV `d0, `s0",
                    SL(munchExp(des)),
                    SL(munchExp(src))));
                return;
            }
        }
        case T_EXP:
        {
            munchExp(stm->u.EXP);
            return;
        }
    }
    assert(0);

};

AS_instrList F_codegen(F_frame f, T_stmList stmList)
{
    T_stmList ptr = stmList;
    while(ptr)
    {
        munchStm(ptr->head);
        ptr = ptr->tail;
    }
    return instrFirst;
}
